/*
 * Axelor Business Solutions
 *
 * Copyright (C) 2005-2021 Axelor (<http://axelor.com>).
 *
 * This program is free software: you can redistribute it and/or  modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
(function () {

"use strict";

var ui = angular.module("axelor.ui");

ui.ViewCtrl = ViewCtrl;
ui.ViewCtrl.$inject = ['$scope', 'DataSource', 'ViewService'];

function ViewCtrl($scope, DataSource, ViewService) {

  $scope._viewParams = $scope._viewParams || $scope.selectedTab;
  if (!$scope._viewParams) {
    throw "View parameters are not provided.";
  }

  var params = $scope._viewParams;

  $scope._views = ViewService.accept(params);
  $scope._viewType = params.viewType;

  if ($scope.$parent && $scope.$parent._model) {
    $scope._parentModel = $scope.$parent._model;
  }

  $scope._model = params.model;
  $scope._fields = {};

  $scope._dataSource = null;
  $scope._domain = params.domain;
  $scope._context = params.context;

  if (params.model) {
    $scope._dataSource = DataSource.create(params.model, params);
  }

  $scope._defer = function() {
    return ViewService.defer();
  };

  $scope.loadView = function(viewType, viewName) {
    var view = $scope._views[viewType] || {
      type: viewType,
      name: viewName
    };
    var ctx = $scope._context;
    if ($scope.getContext) {
      ctx = $scope.getContext();
    }
    return ViewService.getMetaDef($scope._model, view, ctx);
  };

  $scope.loadFields = function() {
    return ViewService.getFields($scope._model);
  };

  $scope.updateRoute = function() {
    this.$emit("on:update-route");
  };

  $scope.getRouteOptions = function() {
    throw "Not Implemented.";
  };

  $scope.setRouteOptions = function(options) {
    throw "Not Implemented.";
  };

  var switchedTo = null;

  $scope.switchTo = function(viewType, /* optional */ callback) {

    var view = $scope._views[viewType];
    if (!view) {
      return;
    }

    var promise = view.deferred.promise;
    promise.then(function(viewScope){

      if (!viewScope || switchedTo === viewType) {
        return;
      }

      switchedTo = viewType;

      $scope._viewTypeLast = $scope._viewType === 'form' ? $scope._viewTypeLast : $scope._viewType;
      $scope._viewType = viewType;
      $scope._viewParams.viewType = viewType; //XXX: remove
      $scope._viewParams.$viewScope = viewScope;

      viewScope.show();

      if (viewScope.updateRoute) {
        viewScope.updateRoute();
      }

      if (callback) {
        callback(viewScope);
      }
    });
  };

  if (!params.action) {
    return;
  }

  // hide toolbar button titles
  $scope.tbTitleHide = !axelor.config['view.toolbar.titles'];

  function switchAndEdit(id, readonly) {
    $scope.switchTo('form', function(scope) {
      scope._viewPromise.then(function() {
        scope.doRead(id).success(function(record) {
          scope.edit(record);
          scope.setEditable(!readonly);
        });
      });
    });
  }

  // show single or default record if specified
  var context = params.context || {};
  if (context._showSingle || context._showRecord) {
    var ds = $scope._dataSource;
    var forceEdit = (params.params||{}).forceEdit === true;

    if (context._showRecord > 0) {
      params.viewType = "form";
      return $scope.switchTo('form');
    }

    return ds.search({
      offset: 0,
      limit: 2,
      fields: ["id"]
    }).success(function(records, page){
      if (page.total === 1 && records.length === 1) {
        return switchAndEdit(records[0].id, !forceEdit);
      }
      return $scope.switchTo($scope._viewType || 'grid');
    });
  }

  // switch to the the current viewType
  $scope.switchTo($scope._viewType || 'grid');
}

/**
 * Base controller for DataSource views. This controller should not be used
 * directly but actual controller should inherit from it.
 *
 */
ui.DSViewCtrl = function DSViewCtrl(type, $scope, $element) {

  if (!type) {
    throw "No view type provided.";
  }
  if (!$scope._dataSource) {
    throw "DataSource is not provided.";
  }

  $scope._viewResolver = $scope._defer();
  $scope._viewPromise = $scope._viewResolver.promise;

  var ds = $scope._dataSource;
  var view = $scope._views[type] || {};
  var viewPromise = null;
  var hiddenButtons = {};

  var params = $scope._viewParams;

  if (params.params && params.params.limit) {
    if (ds && ds._page) {
      ds._page.limit = +(params.params.limit) || ds._page.limit;
    }
  }

  $scope.fields = {};
  $scope.fields_related = {};
  $scope.schema = null;

  $scope.show = function() {
    if (!viewPromise) {
      viewPromise = $scope.loadView(type, view.name);
      viewPromise.then(function(meta){
        var schema = meta.view;
        var fields = meta.fields || params.fields;
        var toolbar = [];
        _.each(schema.toolbar, function(button){
          button.custom = true;
          if (/^(new|edit|save|delete|copy|cancel|back|refresh|search|export|log|files)$/.test(button.name)) {
            hiddenButtons[button.name] = button;
            button.custom = false;
          }
          toolbar.push(button);
        });
        var forceTitle = params.forceTitle;
        if (forceTitle === undefined) {
          forceTitle = (params.params||{}).forceTitle;
        }
        if (!forceTitle && schema.title) {
          $scope.viewTitle = schema.title;
        }
        $scope.fields = fields;
        $scope.fields_related = meta.related;
        $scope.schema = schema;
        $scope.toolbar = toolbar;
        $scope.menubar = schema.menubar;

        $scope.toolbarAsMenu = _.isEmpty(toolbar) ? null : [{
          icon: 'fa-wrench',
          isButton: true,
          items: _.map(toolbar, function (item) {
            return _.extend({}, item, {
              name: item.name,
              action: item.onClick,
              title: item.title || item.autoTitle || item.name
            });
          })
        }];

        // watch on view.loaded to improve performance
        schema.loaded = true;
      });
    }

    $scope.onShow(viewPromise);
  };

  $scope.onShow = function(promise) {

  };

  $scope.canNext = function() {
    return ds && ds.canNext();
  };

  $scope.canPrev = function() {
    return ds && ds.canPrev();
  };

  $scope.getPageSize = function() {
    var page = ds && ds._page;
    if (page) {
      return page.limit;
    }
    return 40;
  };

  $scope.setPageSize = function(value) {
    var page = ds && ds._page,
      limit = Math.max(0, +value) || 40;
    if (page && page.limit != limit) {
      page.limit = limit;
      $scope.onRefresh();
    }
  };

  var can = (function (scope) {
    var fn = null;
    var perms = {
      'new': 'create',
      'copy': 'create',
      'edit': 'write',
      'save': 'write',
      'delete': 'remove',
      'archive': 'remove',
      'export': 'export'
    };
    var actions = {
      'new': 'canNew',
      'edit': 'canEdit',
      'save': 'canSave',
      'copy': 'canCopy',
      'delete': 'canDelete',
      'archive': 'canArchive',
      'attach': 'canAttach'
    };

    function attr(which) {
      if (fn === null && _.isFunction(scope.attr)) {
        fn = scope.attr;
      }
      return !fn || fn(which) !== false;
    }

    function perm(which) {
      return which === undefined || scope.hasPermission(which);
    }

    return function can(what) {
      return attr(actions[what]) && perm(perms[what]);
    };
  })($scope);

  $scope.hasButton = function(name) {
    if (!can(name)) {
      return false;
    }
    if (_(hiddenButtons).has(name)) {
      var button = hiddenButtons[name];
      if (button.isHidden) {
        return !button.isHidden();
      }
      return !button.hidden;
    }
    return true;
  };

  $scope.hasPermission = function(perm) {
    var view = $scope.schema;
    var defaultValue = arguments.length === 2 ? arguments[1] : true;
    if (!view || !view.perms) return defaultValue;
    var perms = view.perms;
    var permitted = perms[perm];
    if (permitted === undefined) {
      return defaultValue;
    }
    return _.toBoolean(permitted);
  };

  $scope.isPermitted = function(perm, record, callback) {
    var ds = this._dataSource;
    ds.isPermitted(perm, record).success(function(res){
      var errors = res.errors;
      if (errors) {
        return axelor.dialogs.error(errors.read);
      }
      callback();
    });
  };

  $scope.canShowToolbar = function() {
    var params = ($scope._viewParams || {}).params;
    if (params && params['show-toolbar'] === false) {
      return false;
    }
    return true;
  };

  $scope.hasHelp = function() {
    var view = $scope.schema;
    return view ? view.helpLink : false;
  };

  $scope.onShowHelp = function() {
    if ($scope.hasHelp()) {
      window.open($scope.schema.helpLink);
    }
  };

  if (view.deferred) {
    view.deferred.resolve($scope);
  }

  $scope.$on('on:tab-reload', function(e, tab) {
    if ($scope === e.targetScope && $scope.onRefresh) {
      $scope.onRefresh();
    }
  });
};

ui.directive('uiViewPane', function() {

  return {
    replace: true,
    controller: ['$scope', '$attrs', 'DataSource', 'ViewService', function ($scope, $attrs, DataSource, ViewService) {

      var params = $scope.$eval($attrs.uiViewPane);

      $scope._viewParams = params;
      ViewCtrl.call(this, $scope, DataSource, ViewService);

      $scope.viewList = [];
      $scope.viewType = null;

      var switchTo = $scope.switchTo;
      $scope.switchTo = function (type, callback) {
        var view = $scope._views[type];
        if (view && $scope.viewList.indexOf(type) === -1) {
          $scope.viewList.push(type);
        }
        var viewScope = !$scope._isPopup && $scope.selectedTab && $scope.selectedTab.$viewScope;
        if (viewScope && viewScope.viewType === 'form' && viewScope.viewType !== type) {
          viewScope.$$resetForm();
        }
        $scope.viewType = type;
        return switchTo(type, callback);
      };

      $scope.$watch('selectedTab.viewType', function viewTypeWatch(type) {
        var params = $scope._viewParams;
        if (params && params.$viewScope !== ($scope.selectedTab || {}).$viewScope) {
          return;
        }
        if ($scope.viewType !== type && type) {
          $scope.switchTo(type);
        }
      });

      $scope.viewTemplate = function (type) {
        var tname = "ui-template:" + type;
        var template = type;
        if (params.params && params.params[tname]) {
          template = params.params[tname];
        }
        return 'partials/views/' + template + '.html';
      };

      var type = params.viewType || params.type;
      $scope.keepAttached = $scope._isPopup || (params.params||{}).popup || type === 'html';
      $scope.switchTo(type);
    }],
    link: function(scope, element, attrs) {

    },
    template:
      "<div class='view-pane' ui-attach='keepAttached || tab.selected'>" +
        "<div class='view-container'" +
        " ng-repeat='type in viewList'" +
        " ui-show='type === viewType'" +
        " ui-attach-scroll ui-attach='keepAttached || type == viewType'" +
        " ng-include='viewTemplate(type)'></div>" +
      "</div>"
  };
});

ui.directive('uiViewPopup', function() {

  return {
    controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
      var params = $scope.$eval($attrs.uiViewPopup);

      $scope.tab = params;
      $scope._isPopup = true;

      $scope.onHotKey = function (e, action) {
        return false;
      };

      var canClose = false;

      $scope.onOK = function () {
        $scope.closeTab($scope.tab, function() {
          canClose = true;
          $element.dialog('close');
        });
      };

      $scope.onBeforeClose = function(e) {
        if (canClose) {
          return;
        }
        e.preventDefault();
        e.stopPropagation();
        $scope.onOK();
      };

      $scope.onPopupClose = function () {
        var tab = $scope.tab,
          params = tab.params || {},
          parent = tab.$popupParent;

        while (parent && parent.$$destroyed && parent.tab) {
          parent = parent.tab.$popupParent;
        }
        if (parent && parent.reload && params.popup === "reload") {
          parent.reload();
        }
        $scope.$applyAsync();
      };

      $scope.onPopupOK = function () {
        var viewScope = $scope._viewParams.$viewScope;
        if (!viewScope.onSave || (!viewScope.isDirty() && viewScope.id)) {
          return $scope.onOK();
        }
        return viewScope.onSave({ fireOnLoad: false }).then(function(record, page) {
          viewScope.edit(record);
          viewScope.$timeout($scope.onOK.bind($scope));
        });
      };

      params = $scope.tab.params || {};
      if (params['popup-save'] === false) {
        $scope.onPopupOK = false;
      }
    }],
    link: function (scope, element, attrs) {

      scope.$watch('viewTitle', function viewTitleWatch(title) {
        scope._setTitle(title);
      });

      scope.waitForActions(function () {
        if (scope._viewParams.viewType === 'html') {
          scope.viewTitle = scope.tabTitle(scope._viewParams);
          scope._doShow();
          return;
        }

        var unwatch = scope.$watch("_viewParams.$viewScope.schema.loaded", function viewLoadedWatch(loaded) {
          if (!loaded) {
            return;
          }
          unwatch();
          var viewScope = scope._viewParams.$viewScope;
          var viewPromise = viewScope._viewPromise;
          scope.viewTitle = scope.tabTitle(scope._viewParams);
          scope.$broadcast('grid:adjust-size', viewScope);
          scope._doShow(viewPromise);
        });
      });
    },
    replace: true,
    template:
      '<div ui-dialog ui-dialog-size x-resizable="true" x-on-close="onPopupClose" x-on-ok="onPopupOK" x-on-before-close="onBeforeClose">' +
        '<div ui-view-pane="tab"></div>' +
      '</div>'
  };
});

ui.directive('uiRecordPager', function(){

  return {
    replace: true,
    link: function(scope, element, attrs) {

      var elText = element.find('.record-pager-text').show(),
        elChanger = element.find('.record-pager-change').hide(),
        elInput = elChanger.find('input');

      scope.showText = attrs.uiRecordPager !== "no-text";

      function updatePageSize() {
        var size = +(elInput.val()) || 0;
        if (scope.setPageSize && size > 0) {
          scope.setPageSize(size);
        }
        elText.add(elChanger).toggle();
      }

      elText.click(function(e) {
        elText.add(elChanger).toggle();
        elInput.zIndex(elInput.parent().zIndex() + 1).focus().select();
      });

      elInput.on('click', function () {
        elInput.zIndex(elInput.parent().zIndex() + 1).focus().select();
      });

      elChanger.on('click', 'button',  function() {
        updatePageSize();
      });

      elChanger.keyup(function(e) {
        if(e.keyCode == 13) { // ENTER
          updatePageSize();
          }
      });

    },
    template:
    '<div class="record-pager hidden-phone">'+
      '<span ng-show="showText">'+
        '<span class="record-pager-text">{{pagerText()}}</span>'+
      '<span class="input-append record-pager-change">'+
        '<input type="text" style="width: 30px;" value="{{getPageSize()}}">'+
        '<button type="button" class="btn add-on"><i class="fa fa-check"></i></button>'+
      '</span>'+
      '</span>'+
      '<div class="btn-group">'+
        '<button class="btn" ng-disabled="!canPrev()" ng-click="onPrev()"><i class="fa fa-chevron-left"></i></button>'+
        '<button class="btn" ng-disabled="!canNext()" ng-click="onNext()"><i class="fa fa-chevron-right"></i></button>'+
      '</div>'+
      '</div>'
  };
});

ui.directive('uiViewCustomize', ['NavService', function(NavService) {

  return {
    scope: true,
    link: function (scope, element, attrs) {

      scope.canShow = function () {
        if (!axelor.config['user.technical']) {
          return false;
        }
        var viewScope = (scope.selectedTab || {}).$viewScope;
        var view = viewScope && viewScope.schema;
        return view && (view.viewId || view.modelId);
      };

      scope.hasViewID = function () {
        var viewScope = (scope.selectedTab || {}).$viewScope;
        var view = viewScope && viewScope.schema;
        return view && view.viewId;
      };

      scope.hasModelID = function () {
        var viewScope = (scope.selectedTab || {}).$viewScope;
        var view = viewScope && viewScope.schema;
        return view && view.modelId;
      };

      scope.hasActionID = function () {
        return (scope.selectedTab || {}).actionId;
      };

      scope.onShowView = function () {
        var id = scope.hasViewID();
        NavService.openTabByName("form::com.axelor.meta.db.MetaView", {
          mode: "edit",
          state: id
        });
        scope.waitForActions(function () {
          var vs = (scope.selectedTab || {}).$viewScope;
          if (vs && vs.setEditable) {
            vs.setEditable();
          }
        });
      };

      scope.onShowModel = function () {
        var id = scope.hasModelID();
        NavService.openTabByName("form::com.axelor.meta.db.MetaModel", {
          mode: "edit",
          state: id
        });
      };

      scope.onShowAction = function () {
        var id = scope.hasActionID();
        NavService.openTabByName("form::com.axelor.meta.db.MetaAction", {
          mode: "edit",
          state: id
        });
      };
    },
    replace: true,
    template:
      "<ul ng-show='canShow()' class='nav menu-bar view-customize hidden-phone'>" +
        "<li class='dropdown menu'>" +
          "<a class='dropdown-toggle btn' data-toggle='dropdown' title='{{ \"Customize...\" | t}}'>" +
            "<i class='fa fa-wrench'></i>" +
          "</a>" +
          "<ul class='dropdown-menu pull-right'>" +
            "<li><a ng-click='onShowView()' ng-show='hasViewID()'>View...</a></li>" +
            "<li><a ng-click='onShowModel()' ng-show='hasModelID()'>Model...</a></li>" +
            "<li><a ng-click='onShowAction()' ng-show='hasActionID()'>Action...</a></li>" +
          "</ul>" +
        "</li>" +
      "</ul>"
  };
}]);

function viewSwitcher(scope, element, attrs) {

  var params = (scope._viewParams || scope.tab);
  var viewTypes = _.pluck(params.views, 'type');

  if ((params.viewType || params.type) === 'dashboard') {
    element.hide();
    return;
  }

  element.find("[x-view-type]").click(function(e) {
    if (this.disabled) {
      return;
    }
    var type = $(this).attr("x-view-type");
    var vs = params.$viewScope || (scope.selectedTab || {}).$viewScope;
    var ds = vs._dataSource;
    var page = ds && ds._page;

    if (type === "form" && page) {
      if (page.index === -1) page.index = 0;
    }

    if ((scope.selectedTab || {}).viewType === 'grid') {
      var items = vs.getItems() || [];
      var index = _.first(vs.selection || []);
      if (index === undefined && items.length === 0 && vs.schema.canNew === false) {
        return;
      }
      if (index !== undefined) page.index = index;
    }

    vs.switchTo(type);
    vs.$applyAsync();
  }).each(function() {
    var type = $(this).attr("x-view-type");
    if (viewTypes.indexOf(type) === -1) {
      $(this).hide();
    }
  });

  var watchExpr = scope._viewParams ? '_viewType' : 'tab.viewType';
  scope.$watch(watchExpr, function viewTypeWatch(type) {
    element.find("[x-view-type]").attr("disabled", false);
    element.find("[x-view-type][x-view-type=" + type + "]").attr("disabled", true);
  });
}

ui.directive('uiViewSwitcher', function(){
  return {
    scope: true,
    link: function(scope, element, attrs) {
      element.parents('.view-container:first').addClass('has-toolbar');
      viewSwitcher(scope, element, attrs);
    },
    replace: true,
    template:
    '<div class="view-switcher pull-right hidden-phone">'+
        '<div class="btn-group">'+
          '<button class="btn" x-view-type="grid"><i class="fa fa-list"></i></button>'+
        '<button class="btn" x-view-type="cards"><i class="fa fa-th-large"></i></button>'+
        '<button class="btn" x-view-type="kanban"><i class="fa fa-columns"></i></button>'+
          '<button class="btn" x-view-type="calendar"><i class="fa fa-calendar"></i></button>'+
          '<button class="btn" x-view-type="gantt"><i class="fa fa-calendar"></i></button>'+
          '<button class="btn" x-view-type="chart"><i class="fa fa-bar-chart-o"></i></button>'+
          '<button class="btn" x-view-type="form"	><i class="fa fa-file-text-o"></i></button>'+
        '</div>'+
    '</div>'
  };
});

ui.directive('uiViewSwitcherMenu', function(){
  return {
    scope: true,
    link: function(scope, element, attrs) {
      viewSwitcher(scope, element, attrs);
    },
    replace: true,
    template:
      "<span class='view-switch-menu dropdown pull-right'>" +
        "<a href='' class='dropdown-toggle' data-toggle='dropdown'><i class='fa fa-ellipsis-v'></i></a>" +
        "<ul class='dropdown-menu'>" +
           "<li><a href='' x-view-type='grid' x-translate>Grid</a></li>" +
           "<li><a href='' x-view-type='cards' x-translate>Cards</a></li>" +
           "<li><a href='' x-view-type='kanban' x-translate>Kanban</a></li>" +
           "<li><a href='' x-view-type='calendar' x-translate>Calendar</a></li>" +
           "<li><a href='' x-view-type='gantt' x-translate>Gantt</a></li>" +
           "<li><a href='' x-view-type='chart' x-translate>Chart</a></li>" +
           "<li><a href='' x-view-type='form' x-translate>Form</a></li>" +
        "</ul>" +
      "</span>"
  };
});

ui.directive('uiHotKeys', function() {

  var keys = {
    '': {
      120: 'toggle-menu'// F9
    },
    'ctrl': {
      45: 'new',        // insert
      69: 'edit',       // e
      83: 'save',       // s
      68: 'delete',     // d
      82: 'refresh',    // r
      74: 'prev',       // j
      75: 'next',       // k
      77: 'focus-menu', // m
      81: 'close'       // q
    },
    'alt': {
      70: 'search',     // f
      71: 'select',     // g
    }
  }

  return function(scope, element, attrs) {

    var loginWindow = $("#loginWindow");

    $(document).on('keydown.axelor-keys', function (e) {

      if (loginWindow.is(":visible")) {
        return;
      }

      // disable backspace as back button
      if (e.which === 8 && e.target === document.body) {
        e.preventDefault();
        return false;
      }

      // no shortcuts defined with shift or meta modifiers
      if (e.shiftKey || e.metaKey) {
        return;
      }

      var modifierKeys = '';

      if (e.ctrlKey) {
        modifierKeys += 'ctrl';
      }

      if (e.altKey) {
        modifierKeys += 'alt';
      }

      var action = (keys[modifierKeys] || {})[e.which];

      if (!action) {
        return;
      }

      if (action === "toggle-menu") {
        $('#offcanvas-toggle a').click();
        return false;
      }

      if (action === "focus-menu") {
        $('.sidebar .nav-search-toggle > i').click();
        return false;
      }

      var tab = scope.selectedTab,
        dlg = $('[ui-editor-popup]:visible:last,[ui-view-popup]:visible:last,[ui-dms-popup]:visible:last').first(),
        vs = tab ? tab.$viewScope : null;

      if (dlg.length) {
        vs = dlg.scope();
      }

      if (!vs) {
        return;
      }

      if (action === "close") {
        scope.closeTab(tab, function() {
          scope.$applyAsync();
        });
        return false;
      }

      if (action === "search") {
        var filterBox = $('.filter-box :input:visible');
        if (filterBox.length) {
          filterBox.focus().select();
          return false;
        }
      }

      if (_.isFunction(vs.onHotKey)) {
        return vs.onHotKey(e, action);
      }
    });

    scope.$on('$destroy', function() {
      $(document).off('keydown.axelor-keys');
    });
  };
});

})();
